package com.kr.weitao.util;

import java.io.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;


/**
 * Created by iBo on 16/3/24.
 */

public class MD5Util {
	private MessageDigest md;
	private long length;

	/**
	 * File that will be checksummed.
	 */
	//private File file;
	private InputStream input;
	/**
	 * Checksum
	 */
	private String checksum;

	/**
	 * Hex digits.
	 */
	private static final char[] hexadecimal =
			{
					'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
					'a', 'b', 'c', 'd', 'e', 'f'
			};

	public MD5Util() {
		try {
			md = MessageDigest.getInstance("MD5");
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
	}

	public void setInputStream(InputStream stream) {
		this.input = stream;
	}


	/**
	 * 将16进制范围的字母或数字的字符转换成对应的整数， 0－9 a－f｜A－F则转换成10－15
	 * 
	 * @param ch
	 * @return
	 */
	private static char char2Int(char ch) {
		if (ch >= '0' && ch <= '9')
			return (char) (ch - '0');
		if (ch >= 'a' && ch <= 'f')
			return (char) (ch - 'a' + 10);
		if (ch >= 'A' && ch <= 'F')
			return (char) (ch - 'A' + 10);
		return ' ';
	}

	/**
	 * 将两个字符转换成一个字节表示
	 * 
	 * @param str
	 * @return
	 */
	private static byte str2Bin(char[] str) {
		byte chn;
		char[] tempWord = new char[2];

		tempWord[0] = char2Int(str[0]); // make the B to 11 -- 00001011
		tempWord[1] = char2Int(str[1]); // make the 0 to 0 -- 00000000

		chn = (byte) ((tempWord[0] << 4) | tempWord[1]); // to change the BO to
		// 10110000

		return chn;
	}

	/**
	 * 将32长度的字符数组压缩生成标准的16位字节数组的MD5
	 * 
	 * @param md5chs32len 32长度的MD5字符串的字符数组
	 * @return
	 */
	public static byte[] compress(char[] md5chs32len) {
		char[] tem = new char[2];
		byte[] sDst = new byte[md5chs32len.length / 2];
		int j = 0;
		for (int i = 0; i + 1 < md5chs32len.length; i += 2) {
			tem[0] = md5chs32len[i];
			tem[1] = md5chs32len[i + 1];
			sDst[j++] = (byte) (str2Bin(tem));
		}
		return sDst;
	}

	/**
	 * 将16字节的MD5数组转换成32长度的字符串
	 * 
	 * @param md5b16
	 * @return
	 */
	public static String unCompress(byte[] md5b16) {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < md5b16.length; i++) {
			byte b = md5b16[i];
			sb.append(Integer.toHexString((b >> 4) & 0x0F));
			sb.append(Integer.toHexString(b & 0x0F));
		}
		return sb.toString().toUpperCase();
	}

	/**
	 * 将明文用MD5算法加密后并压缩成16字节数组
	 *
	 * @param str 明文
	 * @return 16字节数组
	 */
	public static byte[] getMD5Byte16(String str) {
		if (str == null || "".equals(str)) {
			return null;
		}
		try {
			MessageDigest messageDigest = MessageDigest.getInstance("MD5");
			messageDigest.update(str.getBytes("UTF-8"));
			return messageDigest.digest();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		return null;
	}

	static char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
			'9', 'A', 'B', 'C', 'D', 'E', 'F' };

	/**
	 * 把byte[]数组转换成十六进制字符串表示形式
	 *
	 * @param tmp 要转换的byte[]
	 * @return 十六进制字符串表示形式
	 */
	private static String byteToHexString(byte[] tmp) {
		String s;
		// 用字节表示就是 16 个字节
		char str[] = new char[16 * 2]; // 每个字节用 16 进制表示的话，使用两个字符，
		// 所以表示成 16 进制需要 32 个字符
		int k = 0; // 表示转换结果中对应的字符位置
		for (int i = 0; i < 16; i++) { // 从第一个字节开始，对 MD5 的每一个字节
			// 转换成 16 进制字符的转换
			byte byte0 = tmp[i]; // 取第 i 个字节
			str[k++] = hexDigits[byte0 >>> 4 & 0xf]; // 取字节中高 4 位的数字转换,
			// >>> 为逻辑右移，将符号位一起右移
			str[k++] = hexDigits[byte0 & 0xf]; // 取字节中低 4 位的数字转换
		}
		s = new String(str); // 换后的结果转换为字符串
		return s;
	}

	/**
	 * Set the checksum
	 *
	 * @param checksum The checksum.
	 */
	public void setChecksum(String checksum) {
		this.checksum = checksum;
	}

	/**
	 * Get the checksum
	 *
	 * @return The calculated checksum.
	 */
	public String getChecksum() {
		return checksum;
	}

	/**
	 * Encodes the 128 bit (16 bytes) MD5 into a 32 character String.
	 *
	 * @param binaryData Array containing the digest
	 * @return Encoded MD5, or null if encoding failed
	 */
	public String encode(byte[] binaryData) {

		if (binaryData.length != 16) {
			return null;
		}

		char[] buffer = new char[32];

		for (int i = 0; i < 16; i++) {
			int low = (int) (binaryData[i] & 0x0f);
			int high = (int) ((binaryData[i] & 0xf0) >> 4);
			buffer[i * 2] = hexadecimal[high];
			buffer[i * 2 + 1] = hexadecimal[low];
		}

		return new String(buffer);
	}

	/**
	 * Pull in static content and store it
	 *
	 * @param file The file to read.
	 * @return The bytes of the file.
	 * @throws Exception If an error occurs reading in the file's bytes.
	 */
	public byte[] getBytes(File file)
			throws Exception {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		InputStream stream = new FileInputStream(file);

		byte buf[] = new byte[1024];
		int len = 0;

		while ((len = stream.read(buf, 0, 1024)) != -1) {
			baos.write(buf, 0, len);
		}

		return baos.toByteArray();
	}

	/**
	 * Perform the MD5-Sum work.
	 *
	 * @throws Exception If an error occurs while calculating the sum.
	 */
	public void execute()
			throws IOException {
        /*if ( file.exists() == false )
        {
            throw new IOException("File "+ file.getAbsolutePath() + " not exists.");
        }*/
		readInputStream();
		setChecksum(encode(md.digest()));
	}

	private void update(byte[] buffer, int offset, int len) {
		md.update(buffer, offset, len);
		length += len;
	}

	private void readInputStream() throws IOException {
		BufferedInputStream bis = null;
		try {

			bis = new BufferedInputStream(input);
			reset();
			int len = 0;
			byte[] buffer = new byte[8192];
			while ((len = bis.read(buffer)) > -1) {
				update(buffer, 0, len);
			}

		} finally {
			if (bis != null) try {
				bis.close();
			} catch (Exception e) {
			}
		}
	}

	private void reset() {
		md.reset();
		length = 0;
	}

	/**
	 * Get MD5 Checksum String of file content
	 *
	 * @param str
	 */
	public static String toCheckSumStr(String str) throws IOException {
		MD5Util sum = new MD5Util();
		sum.setInputStream(new ByteArrayInputStream(str.getBytes("UTF-8")));
		sum.execute();
		return sum.getChecksum();
	}

	/**
	 * Get MD5 Checksum String of file content
	 *
	 * @param fileStream any stream for file
	 */
	public static String toCheckSum(InputStream fileStream) throws IOException {
		MD5Util sum = new MD5Util();
		sum.setInputStream(fileStream);
		sum.execute();
		return sum.getChecksum();
	}

	/**
	 * Get MD5 Checksum String of file content
	 *
	 * @param file should be a file name with path, can not be jar entry
	 */
	public static String toCheckSum(String file) throws IOException {
		MD5Util sum = new MD5Util();
		FileInputStream fileStream = new FileInputStream(file);
		try {
			sum.setInputStream(fileStream);
			sum.execute();
			return sum.getChecksum();
		} finally {
			if (fileStream != null) {
				try {
					fileStream.close();
				} catch (Exception e) {
				}
			}
		}
	}


	/**
	 * MD5加密
	 *
	 * @param ts     时间戳
	 * @param userId ID
	 * @param skey   值
	 * @return
	 */
	public static String getSign(String ts, String userId, String skey) {
		String sign = null;
		try {
			new MD5Util();
			sign = MD5Util.toCheckSumStr(userId + ts + skey);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return sign;
	}

	/**
	 * 获取MD5加密后的32位字符串
	 *
	 * @param str 明文
	 * @return 返回MD5加密后的32位串
	 */
	public static String getMD5Str32(String str) {
		if (str == null || "".equals(str)) {
			return null;
		}
		return byteToHexString(getMD5Byte16(str));
	}


}
